package cn.t.facade.order.job;


import cn.t.constants.CommonConstants;
import cn.t.constants.ParamConstants;
import cn.t.converter.device.DeviceDataDTOConverter;
import cn.t.converter.order.ExceptionLogDTOConverter;
import cn.t.converter.order.OrderDTOConverter;
import cn.t.core.mode.BaseDO;
import cn.t.dto.device.CargowaySkuDTO;
import cn.t.dto.device.DeviceUpstreamDO;
import cn.t.dto.device.OpenCloseWeightVO;
import cn.t.dto.device.vo.QtyVO;
import cn.t.dto.device.vo.WeightVO;
import cn.t.enums.ActionTypeEnum;
import cn.t.enums.OptionTypeEnum;
import cn.t.enums.device.DeviceTypeEnum;
import cn.t.enums.order.ErrorTypeEnum;
import cn.t.enums.order.MsvmOrderStatusEnum;
import cn.t.facade.android.DeviceCommunicationFacade;
import cn.t.facade.device.CargowaySkuFacade;
import cn.t.facade.order.ExceptionLogFacade;
import cn.t.model.device.DeviceDO;
import cn.t.model.device.DeviceDataDO;
import cn.t.model.order.ExceptionLogDO;
import cn.t.model.order.OrderDO;
import cn.t.model.user.UserQueryDO;
import cn.t.service.device.DeviceDataService;
import cn.t.service.device.DeviceService;
import cn.t.service.user.UserQueryService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author l
 */
@Slf4j
@Component
public class FixCloseDoorDataFacade {
    @Value("${job.fixCloseDoorData.days}")
    private int days;
    @Resource
    private DeviceService deviceService;
    @Resource
    private UserQueryService userQueryService;
    @Resource
    private CargowaySkuFacade cargowaySkuFacade;
    @Resource
    private DeviceCommunicationFacade deviceCommunicationFacade;
    @Resource
    private ExceptionLogFacade exceptionLogFacade;
    @Resource
    private OrderDTOConverter orderDTOConverter;
    @Resource
    private ExceptionLogDTOConverter exceptionLogDTOConverter;
    @Resource
    private DeviceDataService deviceDataService;
    @Resource
    private DeviceDataDTOConverter deviceDataDTOConverter;
//TODO
    public void fixCloseDoorData() {
        // 获取时间过去5天的前到现在两个小时的数据
        Date startTime = Date.from(LocalDateTime.now().minusDays(days).withHour(0).withMinute(0).minusSeconds(0).
                withNano(0).atZone(ZoneId.systemDefault()).toInstant());

        //处理2小时之前的，避免影响当前正常领用的数据。
        Date endTime = Date.from(LocalDateTime.now().minusHours(2).withMinute(0).minusSeconds(0).
                withNano(0).atZone(ZoneId.systemDefault()).toInstant());
        DeviceDO deviceDO1 = new DeviceDO();
        deviceDO1.setDeviceTypeList(DeviceTypeEnum.ALL_TYPE_JOB);
        // 查询所有设备
        List<DeviceDO> allDevices = deviceService.findByModel(deviceDO1, CommonConstants.ZERO, Integer.MAX_VALUE, null, null);
        allDevices.forEach(deviceDO -> {
            List<DeviceDataDO> deviceDataList = deviceDataService.queryDeviceDataByTime(startTime, endTime,deviceDO.getId(), OptionTypeEnum.order_open);
            Map<String, DeviceDataDO> orderMap = new HashMap<>();
            //获取这个设备有开门没有关门的开门数据
            List<DeviceDataDO> fixableDeviceData = this.findFixableDeviceData(deviceDataList, orderMap);


            Map<Integer, CargowaySkuDTO> cargoways = cargowaySkuFacade.cargowayMap(deviceDO.getId());
            //获取所有开门数据
            for (DeviceDataDO openDoorData : fixableDeviceData) {
                DeviceDataDO nextOpenDoorData = orderMap.get(openDoorData.getOrderSn());
                if (Objects.isNull(nextOpenDoorData)) {
                    log.error(String.format("未找到对应的关门数据。ordersn1:%s", openDoorData.getOrderSn()));
                    continue;
                }
                DeviceDataDO newCloseDoorData = deviceDataDTOConverter.toCopy(openDoorData);
                newCloseDoorData.setDeviceData(nextOpenDoorData.getDeviceData());
                newCloseDoorData.setAction(ActionTypeEnum.close.getCode());
                 setBaseInfo(newCloseDoorData);
                if (openDoorData.getRecordAt().getTime() > nextOpenDoorData.getRecordAt().getTime()) {
                    log.warn(String.format("开门时间与开门顺序数据冲突。ordersn1:%s, ordersn2:%s", openDoorData.getOrderSn(), nextOpenDoorData.getOrderSn()));
                    newCloseDoorData.setRecordAt(openDoorData.getRecordAt());
                } else {
                    newCloseDoorData.setRecordAt(nextOpenDoorData.getRecordAt());
                }
                fixDeviceData(openDoorData, newCloseDoorData, deviceDO,cargoways);
            }
        });
    }



    private void fixDeviceData(DeviceDataDO openDoorData, DeviceDataDO newCloseDoorData,DeviceDO deviceDO, Map<Integer, CargowaySkuDTO> cargoways) {
        try {
            UserQueryDO userQueryDO1 = new UserQueryDO();
            userQueryDO1.setId(openDoorData.getUserId());
            UserQueryDO userQueryDO  = userQueryService.findUserByFirmId(userQueryDO1).stream().findFirst().orElse(new UserQueryDO());
            createExceptionOrder(openDoorData, newCloseDoorData, cargoways, userQueryDO, deviceDO);
        } catch (Exception e) {
            log.error("修复设备关门无重量数据失败，保存数据异常", e);
            e.printStackTrace();
        }
    }

    public void createExceptionOrder(DeviceDataDO openData, DeviceDataDO closeData, Map<Integer, CargowaySkuDTO> cargoways, UserQueryDO userQueryDO, DeviceDO deviceDO) {
        //重力计算 openCloseWeightVO
        OpenCloseWeightVO openCloseWeightVO = new OpenCloseWeightVO();
        openCloseWeightVO.setOpenScale(getDeviceData(openData.getDeviceData()));
        openCloseWeightVO.setCloseScale(getDeviceData(closeData.getDeviceData()));
        openCloseWeightVO.setCargowayMap(cargoways);
        List<QtyVO> qtyList = deviceCommunicationFacade.calcGravity(openCloseWeightVO).stream().filter(qtyVO -> qtyVO.getWeight() ==null || BigDecimal.valueOf(qtyVO.getWeight()).abs().compareTo(BigDecimal.valueOf(CommonConstants.FIVE)) >= 0).collect(Collectors.toList());


        //order add
        OrderDO orderDO = setOrderInfo(openData, closeData, userQueryDO, deviceDO);


        //没有重量变化记录一条总异常订单
       List<ExceptionLogDO> exceptionLogList = new ArrayList<>();
        if (ObjectUtils.isEmpty(qtyList)) {
            ExceptionLogDO exceptionLogDO=exceptionLogDTOConverter.add2Do(openData,null);
            exceptionLogDO.setUserName(userQueryDO.getUserName());
            exceptionLogDO.setDeviceName(deviceDO.getDeviceName());
            exceptionLogDO.setExceptionType(ErrorTypeEnum.ERROR25.getCode());
            exceptionLogDO.setRemark(ErrorTypeEnum.ERROR25.getName());
            setBaseInfo(exceptionLogDO);
            exceptionLogList.add(exceptionLogDO);
        exceptionLogFacade.fixCloseDataAdd(exceptionLogList,orderDO,closeData);
        return;
        }
        Map<Integer, QtyVO> qtyMap = qtyList.stream().collect(Collectors.toMap(QtyVO::getNo, Function.identity()));
        Map<Integer, WeightVO> weightCloseMap = openCloseWeightVO.getCloseScale().stream().collect(Collectors.toMap(WeightVO::getNo, Function.identity()));


          //仅处理有开关门重量变化的数据，开关门重量数据相同的忽略。
        openCloseWeightVO.getOpenScale().forEach(weightVO -> {
            QtyVO qtyVO = qtyMap.get(weightVO.getNo());
            if (qtyVO != null) {
                CargowaySkuDTO cargowaySkuDTO = cargoways.get(qtyVO.getNo());
                ExceptionLogDO exceptionLogDO=exceptionLogDTOConverter.add2Do(openData,cargowaySkuDTO);
                exceptionLogDO.setBeforeWeight(weightVO.getWeight());
                WeightVO weightClose = weightCloseMap.get(qtyVO.getNo());
                if (weightClose != null) {
                    exceptionLogDO.setAfterWeight(weightClose.getWeight());
                }
                exceptionLogDO.setUserName(userQueryDO.getUserName());
                exceptionLogDO.setDeviceName(deviceDO.getDeviceName());
                exceptionLogDO.setExceptionType(ErrorTypeEnum.ERROR25.getCode());
                exceptionLogDO.setRemark(ErrorTypeEnum.ERROR25.getName());
                setBaseInfo(exceptionLogDO);
                exceptionLogList.add(exceptionLogDO);
            }else {
                log.error("未找到有重量数据变化的对应货道 {}", weightVO.getNo());
            }
        });
        exceptionLogFacade.fixCloseDataAdd(exceptionLogList, orderDO, closeData);
    }

    private OrderDO setOrderInfo(DeviceDataDO open, DeviceDataDO close, UserQueryDO userQueryDO, DeviceDO deviceDO) {
        OrderDO orderDO =orderDTOConverter.addException2Do(open);
        orderDO.setOrderStatus(MsvmOrderStatusEnum.ERR_ORDER.getCode());
        orderDO.setCardCode(userQueryDO.getCardCode());
        orderDO.setGmtOpen(open.getRecordAt());
        orderDO.setGmtClose(close.getRecordAt());
        orderDO.setDeviceName(deviceDO.getDeviceName());
        orderDO.setUserName(userQueryDO.getIndividualName());
        setBaseInfo(orderDO);
        if (userQueryDO.getFirms()!=null){
            userQueryDO.getFirms().stream().filter(firmVO -> firmVO.getCustomerId().equals(open.getFirmId())).findFirst().
                    flatMap(firmVO -> firmVO.getParticipants().stream().filter(participantVO -> participantVO.getParticipantId().equals(open.getParticipantId())).findFirst()).ifPresent(participantVO -> {
                        orderDO.setCostCenterId(participantVO.getCostCenterId());
                        orderDO.setCostCenterName(participantVO.getCostCenterName());
                    });
        }


        return orderDO;
    }

    private static void setBaseInfo(BaseDO baseInfo) {
        baseInfo.setGmtCreated(new Date());
        baseInfo.setGmtModified(new Date());
        baseInfo.setCreator(ParamConstants.WORKORDERTYPESTR);
        baseInfo.setModifier((ParamConstants.WORKORDERTYPESTR));
    }

    public List<WeightVO> getDeviceData(String data) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        try {
            DeviceUpstreamDO deviceUpstreamData = objectMapper.readValue(data, new TypeReference<DeviceUpstreamDO>() {
            });
            return deviceUpstreamData.getScale();

        } catch (JsonProcessingException e) {
            log.warn("数据转换异常 checkData {}{}", data, e.getMessage());

            return null;
        }
    }
    /**
     * 获取同一个设备数据下一个关门数据是否为空
     */
    List<DeviceDataDO> findFixableDeviceData(List<DeviceDataDO> deviceDataList, Map<String, DeviceDataDO> fixableDataMap) {

        ;
        List<DeviceDataDO> fixableData = new LinkedList<>();

        for (int i = 0; i < deviceDataList.size(); i++) {
            DeviceDataDO currentData = deviceDataList.get(i);
            // 判断当前数据是否是开门数据
            if (currentData.getAction() == 0 && i < deviceDataList.size() - 1) {
                String currentOrderSn = currentData.getOrderSn();
                for (int j = i + 1; j < deviceDataList.size(); j++) {
                    DeviceDataDO nextData = deviceDataList.get(j);
                    // 判断下一个数据是否是开门数据
                    if (nextData.getAction() == 0 && !nextData.getOrderSn().equals(currentOrderSn)) {
                        fixableData.add(currentData);
                        fixableDataMap.put(currentOrderSn, nextData);
                        i = j - 1;
                        break;
                    }
                    if (nextData.getAction() == 2 && nextData.getOrderSn().equals(currentOrderSn)) {
                        i = j;
                        break;
                    }
                    if (nextData.getAction() == 2 && !nextData.getOrderSn().equals(currentOrderSn)) {
                        log.error(String.format("设备数据异常！%s, %s", currentOrderSn, nextData.getOrderSn()));
                    }
                }
            }
        }

        return fixableData;
    }
}
